﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.XPath;
using NRank.Algorithms.Strategies;
using NRank.Examples.Helpers;

namespace NRank.Examples
{
	///<summary>
	/// Provides example methods for joining the data.
	///</summary>
	public static class Examples
	{
		/// <summary>
		/// Factory method for DatabaseContext
		/// </summary>
		/// <returns></returns>
		private static DatabaseContext GetContext()
		{
			return new DatabaseContext();
		}

		/// <summary>
		/// Example method how to use RankJoin method
		/// </summary>
		public static void RankJoinExample()
		{
			using (var cx = GetContext())
			{
				var result = cx.Hotels
					.OrderByDescending(h => h.Stars) // input must be sorted

					.RankJoin(

					// join on this collection (must be sorted too)
					cx.Muzeums.OrderByDescending(m => -m.EntranceCost),

					// rank of item in collection 1
					h => h.Stars,

					// rank of item in collection 2
					m => (double)-m.EntranceCost,

					// join result
					(h, m) => new
								{
									Hotel = h.Name,
									Muzeum = m.Name,
									h.Stars,
									m.EntranceCost
								},

					// join condition
					(hotel, muzeum) => (hotel.Area == muzeum.Area), PullingStrategyTypes.Alternating,

					// agregate function
					(hotelRank, muzeumRank) => hotelRank + muzeumRank);

				Print("RankJoin result:", result);
			}
		}

		/// <summary>
		/// Example of hash rank join
		/// </summary>
		public static void HashRankJoinExample()
		{
			using (var cx = GetContext())
			{
				var result = cx.Hotels.OrderByDescending(h => h.Stars)
					.HashRankJoin(

						// join on muzeums (sorted)
						cx.Muzeums.OrderByDescending(m => -m.EntranceCost),

						// hotel rank
						h => h.Stars,

						// muzeum rank
						m => (double)-m.EntranceCost,

						// hotel equality attribute
						h => h.Area,

						// muzeum equality attribute
						m => m.Area,

						// join result
						(h, m) => new
									{
										Hotel = h.Name,
										Muzeum = m.Name,
										h.Stars,
										m.EntranceCost
									},

						// pulling strategy
						PullingStrategyTypes.MinimizeThreshold,

						// agregate function
						(hotelRank, muzeumRank) => hotelRank + muzeumRank
					);

				Print("HashRankJoin result:", result);
			}
		}

		///<summary>
		/// Example of Nested loop rank join algorithm
		///</summary>
		public static void NestedLoopRankJoinExample()
		{
			using (var cx = GetContext())
			{
				var result = cx.Hotels
					.OrderByDescending(h => h.Stars) // input must be sorted

					.NestedLoopRankJoin(

						// join on collection (must be sorted too)
					cx.Muzeums.OrderByDescending(m => -m.EntranceCost),

					// rank of item in collection 1
					h => h.Stars,

					// rank of item in collection 2
					m => (double)-m.EntranceCost,

					// join result
					(h, m) => new
								{
									Hotel = h.Name,
									Muzeum = m.Name,
									h.Stars,
									m.EntranceCost
								},

					// join condition
					(hotel, muzeum) => (hotel.Area == muzeum.Area),

					// agregate function
					(hotelRank, muzeumRank) => hotelRank + muzeumRank);

				Print("RankJoin result:", result);
			}
		}

		///<summary>
		/// Example of Nested loop rank join with equality condition
		///</summary>
		public static void NestedLoopRankJoinEqualityExample()
		{
			using (var cx = GetContext())
			{
				var result = cx.Hotels
					.OrderByDescending(h => h.Stars) // input must be sorted

					.NestedLoopRankJoin(

					// join on collection (must be sorted too)
					cx.Muzeums.OrderByDescending(m => -m.EntranceCost),

					// rank of item in collection 1
					h => h.Stars,

					// rank of item in collection 2
					m => (double)-m.EntranceCost,

					// join result
					(h, m) => new
								{
									Hotel = h.Name,
									Muzeum = m.Name,
									h.Stars,
									m.EntranceCost
								},

					// join condition equality attributes
					h => h.Area,
					m => m.Area,

					// agregate function
					(hotelRank, muzeumRank) => hotelRank + muzeumRank);

				Print("RankJoin result:", result);
			}
		}

		/// <summary>
		/// Example for sorted joining of two collection of integers
		/// </summary>
		public static void TopNumbersFromBothCollections()
		{
			var col1 = new[] { 1, 5, 2, 6, 4, 7, 3, 9, 10 };
			var col2 = new[] { 2, 6, 10, 0, 8, 4 };

			var result = col1.OrderByDescending(i => i).HashRankJoin(
				col2.OrderByDescending(j => j),
				i => i, // item rank in col1
				j => j, // item rank in col2
				i => i, // item equality member in col1
				j => j, // item equality member in col2
				(i, j) => i)
				.UnderlyingItems();

			Print("Top numbers:", result);
		}

		/// <summary>
		/// Example showing how to use data from xml 
		/// </summary>
		public static void JoinXmlData()
		{
			var doc = XmlHelper.GetTestingDocument();

			if (doc == null) return;
			if (doc.Root == null) return;

			var hotels = doc.Root.XPathSelectElements("//hotel");

			var muzeums = doc.Root.XPathSelectElements("//muzeum");

			var result = hotels.OrderByDescending(h => int.Parse(h.Element("stars").Value))
				.RankJoin(muzeums.OrderByDescending(m => -double.Parse(m.Element("entrance").Value)),
				          h => int.Parse(h.Element("stars").Value),
				          m => -double.Parse(m.Element("entrance").Value),
				          (h, m) => new
				                    	{
				                    		HotelName = h.Element("name").Value,
				                    		MuzeumName = m.Element("name").Value
				                    	},
				          (h, m) => h.Element("area").Value == m.Element("area").Value);

			Print("Xml data join:", result);
		}

		/// <summary>
		/// Prints the collection to output
		/// </summary>
		/// <typeparam name="T">Any type</typeparam>
		/// <param name="description">Description printed before the collection itself</param>
		/// <param name="collection">Collection to be printed</param>
		private static void Print<T>(string description, IEnumerable<T> collection)
		{
			Console.WriteLine(description);

			foreach (var t in collection)
			{
				Console.WriteLine(t);
			}

			Console.WriteLine();
		}
	}
}
